/* eslint-disable no-case-declarations */
/* eslint-disable class-methods-use-this */
/* eslint-disable camelcase */
/* eslint-disable max-len */
//
// QCircuit.js: 线路相关的类：QGATE/QLINE/QCIRCUIT
//

import {
  Layout as LayoutDHX,
  Form as FormDHX,
  Window as WindowDHX,
} from 'dhx-suite-package';
import Raphael from 'raphael';
import typeset from '@/utils/globalVariable';
import { formArray, colorPickerConfig } from './formData';
import QSimulator from './QSimulator';
import LayoutContainer from './layoutComposer';

// Colours ////////////////////////////////////////

// const pink = 'rgb(255, 150, 206)';
const deepblue = '#5874CD';
const purple = '#7E6BAD';
const blue = '#60AFCE'; // 单比特输入
const orange = '#EC9875'; // 测量
const grey = '#F5F5F5'; // 底色
const white = '#FFFFFF'; // 量子门上字符
const black = '#000000'; // 线

// Constants //////////////////////////////////////

// const gtext = new Array('', 'ID', 'X', 'Y', 'Z', 'H', 'S', 'SDG', 'T', 'TDG', 'RX', 'RY', 'RZ', 'M', 'SWAP', 'CX', 'CY', 'CZ');

const gmatrix = [
  '',
  'ID=\\left[\\begin{matrix}1 & 0\\\\0 & 1\\end{matrix}\\right]',
  'X=\\left[\\begin{matrix}0 & 1\\\\1 & 0\\end{matrix}\\right]',
  'Y=\\left[\\begin{matrix}0 & -i\\\\i & 0\\end{matrix}\\right]',
  'Z=\\left[\\begin{matrix}1 & 0\\\\0 & -1\\end{matrix}\\right]',
  'H=\\frac{1}{\\sqrt{2}}\\left[\\begin{matrix}1 & 1\\\\1 & -1\\end{matrix}\\right]',
  'S=\\left[\\begin{matrix}1 & 0\\\\0 & i\\end{matrix}\\right]',
  'S^\\dagger=\\left[\\begin{matrix}1 & 0\\\\0 & -i\\end{matrix}\\right]',
  'T=\\left[\\begin{matrix}1 & 0\\\\0 & e^{i\\pi/4}\\end{matrix}\\right]',
  'T^\\dagger=\\left[\\begin{matrix}1 & 0\\\\0 & e^{-i\\pi/4}\\end{matrix}\\right]',
  'RX(\\theta)=\\left[\\begin{matrix}\\cos\\frac{\\theta}2 & -i\\sin\\frac{\\theta}2\\\\-i\\sin\\frac{\\theta}2 & \\cos\\frac{\\theta}2\\end{matrix}\\right]',
  'RY(\\theta)=\\left[\\begin{matrix}\\cos\\frac{\\theta}2 & -\\sin\\frac{\\theta}2\\\\\\sin\\frac{\\theta}2 & \\cos\\frac{\\theta}2\\end{matrix}\\right]',
  'RZ(\\lambda)=\\left[\\begin{matrix}e^{-i\\frac\\lambda2} & 0\\\\0 & e^{i\\frac\\lambda2}\\end{matrix}\\right]',
  'CX=\\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & 1 & 0 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 1 & 0\\end{matrix}\\right]',
  'CY=\\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & 1 & 0 & 0\\\\0 & 0 & 0 & -i\\\\0 & 0 & i & 0\\end{matrix}\\right]',
  'CZ=\\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & 1 & 0 & 0\\\\0 & 0 & 1 & 0\\\\0 & 0 & 0 & -1\\end{matrix}\\right]',
  'SWAP=\\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & 0 & 1 & 0\\\\0 & 1 & 0 & 0\\\\0 & 0 & 0 & 1\\end{matrix}\\right]',
  'MeasureZ',
];

const maxline = 8; // 最大量子比特行数
const maxgate = 14; // 一行最大 gate 数

// Global Variables ///////////////////////////////

const line = new Array(maxline);
let nline = 4; // 当前量子比特行数

const circuit = new Array(maxline);
// var ngate = new Array(maxline); // 当前每行 gate 数

let basicGates;

const obj = new Array(); // 存放所有对象

let onmodifypos = 0;
const ginput = new Array(maxline);

const rectattr = {
  fill: blue, 'stroke-width': 0, 'stroke-linecap': 'round', cursor: 'pointer',
};

const crectattr = {
  fill: purple, 'stroke-width': 0, 'stroke-linecap': 'round', cursor: 'pointer',
};

const clineattr = {
  fill: purple, 'stroke-width': 0, cursor: 'pointer',
};

const swapattr = { stroke: purple, 'stroke-width': 4, cursor: 'pointer' };

const rectframeattr = {
  fill: grey, 'stroke-width': 1, 'stroke-linecap': 'round', 'stroke-dasharray': '--',
};

const textattr = {
  fill: white, 'font-size': 18, cursor: 'pointer',
};

const circleattr = {
  fill: white, 'stroke-width': 0, 'stroke-linecap': 'round', cursor: 'pointer',
};

const editPath = 'M25.31,2.872l-3.384-2.127c-0.854-0.536-1.979-0.278-2.517,0.576l-1.334,2.123l6.474,4.066l1.335-2.122C26.42,4.533,26.164,3.407,25.31,2.872zM6.555,21.786l6.474,4.066L23.581,9.054l-6.477-4.067L6.555,21.786zM5.566,26.952l-0.143,3.819l3.379-1.787l3.14-1.658l-6.246-3.925L5.566,26.952z';
const delePath = 'M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z';
const trashPath = 'M20.826,5.75l0.396,1.188c1.54,0.575,2.589,1.44,2.589,2.626c0,2.405-4.308,3.498-8.312,3.498c-4.003,0-8.311-1.093-8.311-3.498c0-1.272,1.21-2.174,2.938-2.746l0.388-1.165c-2.443,0.648-4.327,1.876-4.327,3.91v2.264c0,1.224,0.685,2.155,1.759,2.845l0.396,9.265c0,1.381,3.274,2.5,7.312,2.5c4.038,0,7.313-1.119,7.313-2.5l0.405-9.493c0.885-0.664,1.438-1.521,1.438-2.617V9.562C24.812,7.625,23.101,6.42,20.826,5.75zM11.093,24.127c-0.476-0.286-1.022-0.846-1.166-1.237c-1.007-2.76-0.73-4.921-0.529-7.509c0.747,0.28,1.58,0.491,2.45,0.642c-0.216,2.658-0.43,4.923,0.003,7.828C11.916,24.278,11.567,24.411,11.093,24.127zM17.219,24.329c-0.019,0.445-0.691,0.856-1.517,0.856c-0.828,0-1.498-0.413-1.517-0.858c-0.126-2.996-0.032-5.322,0.068-8.039c0.418,0.022,0.835,0.037,1.246,0.037c0.543,0,1.097-0.02,1.651-0.059C17.251,18.994,17.346,21.325,17.219,24.329zM21.476,22.892c-0.143,0.392-0.69,0.95-1.165,1.235c-0.474,0.284-0.817,0.151-0.754-0.276c0.437-2.93,0.214-5.209-0.005-7.897c0.881-0.174,1.708-0.417,2.44-0.731C22.194,17.883,22.503,20.076,21.476,22.892zM11.338,9.512c0.525,0.173,1.092-0.109,1.268-0.633h-0.002l0.771-2.316h4.56l0.771,2.316c0.14,0.419,0.53,0.685,0.949,0.685c0.104,0,0.211-0.017,0.316-0.052c0.524-0.175,0.808-0.742,0.633-1.265l-1.002-3.001c-0.136-0.407-0.518-0.683-0.945-0.683h-6.002c-0.428,0-0.812,0.275-0.948,0.683l-1,2.999C10.532,8.77,10.815,9.337,11.338,9.512z';
const swapPath = 'M20,7.5l-5,5M20,7.5l5,5M20,6.5L20,33.5M20,32.5l-5,-5M20,32.5l5,-5';
const daggerPath = 'M28 9L34 9M31 5L31 15';
const measurePath = 'M5,35 A15,15 0 0,1 35,35M20,35L33,17M30,5L35,5L30,10L35,10';
const uparrowPath = 'M20,21l-6,6M20,21l6,6';
const downarrowPath = 'M20,19l-6,-6M20,19l6,-6';

// Useful Functions ///////////////////////////////

function getRow(y) {
  return parseInt((y - 160) / 50, 10);
}

function getCol(x) {
  return parseInt((x - 140) / 60, 10);
}

// 根据列数给出 x 的坐标
function getxPos(col) {
  return 140 + 60 * col;
}

// 根据行数给出 y 的坐标
function getyPos(row) {
  return 160 + 50 * row;
}

function unfocus() {
  const formulaDiv = document.getElementById('formula-div');
  if (formulaDiv && formulaDiv.parentNode) {
    formulaDiv.parentNode.removeChild(formulaDiv);
  }
  for (let i = 0; i < nline; ++i) {
    for (let j = 0; j < maxgate; ++j) {
      if (circuit[i][j] != -1) {
        obj[circuit[i][j]].editset.hide();
        obj[circuit[i][j]].deleset.hide();
      }
    }
  }
}

function refreshCode() {
  let code = '';
  let measurecode = '';
  code += `qreg Q[${nline}];\n//\n`;
  for (let j = 0; j < maxgate; ++j) {
    let hasgate = false;
    for (let i = 0; i < nline; ++i) {
      if (circuit[i][j] == -1) { continue; }
      hasgate = true; // 如果这一列有 gate
      const currGate = obj[circuit[i][j]];
      const gn = currGate.gname;
      // single-input gate
      if (currGate.ginput == 1) {
        if (gn == 'measure') {
          measurecode += `measure Q[${i}];\n`;
        } else if (gn == 'rx' || gn == 'ry' || gn == 'rz') {
          const theta = `(${currGate.theta})`;
          code += `${gn + theta} Q[${i}];\n`;
        } else {
          code += `${gn} Q[${i}];\n`;
        }
      } else { // CX, CY, CZ
        const idxc = currGate.gparams[0];
        code += `${gn} Q[${idxc}], Q[${i}];\n`;
      }
    }
    if (hasgate) { code += '//\n'; }
  }
  code += measurecode;
  LayoutContainer.richtextCode_set(code);
  return code;
}

// Class //////////////////////////////////////////

class ColorPicker {
  constructor() {
    this.init();
    this.reload();
  }

  init() {
    const that = this;
    that.windowContainer = that.windowContainer_create();
    that.windowContainer_event();
    that.layoutContainer = that.layoutContainer_create();
    that.formEdit = that.formEdit_create();
    that.formEdit_event();

    // 挂载
    that.windowContainer.attach(that.layoutContainer);
    that.layoutContainer.getCell('formContainer').attach(that.formEdit);
  }

  reload() {
    const that = this;
    that.colors = {
      Background: grey, Gate: blue, Measure: orange, Text: white, Line: white,
    };
  }

  windowContainer_create() {
    const mywindow = new WindowDHX({
      modal: true,
      title: 'Color Picker',
      closable: true,
      width: 360,
      height: 360,
    });
    return mywindow;
  }

  windowContainer_event() {
    const that = this;
    const myevents = that.windowContainer.events;
    myevents.on('afterhide', () => {
      console.log('hide window');
      for (let i = 0; i < colorPickerConfig.length; ++i) {
        that.colors[colorPickerConfig[i].name] = that.formEdit.getItem(colorPickerConfig[i].name).getValue();
      }
      // 调用改变颜色的函数
      obj[0].setColor(that.colors);
    });
  }

  layoutContainer_create() {
    const mylayout = new LayoutDHX('layout', {
      type: 'none',
      cols: [
        { id: 'formContainer' },
      ],
    });
    return mylayout;
  }

  formEdit_create() {
    const myform = new FormDHX('form', {
      rows: colorPickerConfig,
    });
    return myform;
  }

  formEdit_event() { }

  show() {
    const that = this;
    that.windowContainer.show();
  }
}

class GatePicker {
  constructor() {
    this.ninput = 2;
    this.init();
  }

  init() {
    const that = this;
    that.windowContainer = that.windowContainer_create();
    that.windowContainer_event();
    that.layoutContainer = that.layoutContainer_create();
    that.formEdit = that.formEdit_create();

    // 挂载
    that.windowContainer.attach(that.layoutContainer);
    that.layoutContainer.getCell('formContainer').attach(that.formEdit);
  }

  windowContainer_create() {
    const mywindow = new WindowDHX({
      modal: true,
      width: 300,
      height: 300,
    });
    return mywindow;
  }

  windowContainer_event() {
    const that = this;
    const myevents = that.windowContainer.events;
    myevents.on('afterhide', () => {
      console.log('return from gatepos: %d', onmodifypos);
      // 保存选项
      for (let i = 0; i < that.ninput; ++i) {
        ginput[i] = that.formEdit.getItem(`Q${i.toString()}`).getValue();
      }
      for (let i = 0; i < that.ninput; ++i) {
        console.log(ginput[i]);
      }
      obj[onmodifypos].afterEdit();
      that.close();
      refreshCode();
    });
  }

  layoutContainer_create() {
    const mylayout = new LayoutDHX('layout', {
      type: 'none',
      cols: [
        {
          id: 'formContainer',
          width: '50%',
          height: '100%',
        }, {
          id: 'gateContainer',
          width: '50%',
          height: '100%',
          html: `
                <div class="unselectable" id = "cgate" style="width:500px;height:600px">
                  <svg id="s2" viewBox="0 0 1024 1024" width="25" height="25"></svg>
                </div>
                `,
        },
      ],
    });
    return mylayout;
  }

  formEdit_create() {
    const myform = new FormDHX('form', formArray[2]);
    return myform;
  }

  formEdit_reload(gateinput) {
    const that = this;
    // 根据当前的 qubit 个数（nline）调整下拉选项个数
    function buildoption() {
      const myoption = new Array();
      for (let i = 0; i < nline; ++i) {
        myoption.push({ value: i.toString(), content: i.toString() });
      }
      return myoption;
    }
    for (let i = 0; i < that.ninput; ++i) {
      that.formEdit.getItem(`Q${i.toString()}`).setOptions(buildoption());
      that.formEdit.getItem(`Q${i.toString()}`).setValue(gateinput[i].toString());
    }
  }

  paperGate_reload() { // 画一个选择控制门输入比特的示意图
    const that = this;
    const mypaper = new Raphael('cgate', 300, 300);
    mypaper.path('M10,50H160'); // line1
    mypaper.path('M10,100H160'); // line2
    const px = 40;
    const py = 30;
    mypaper.rect(px + 13, py + 13, 14, 14, 3).attr(crectattr); // crect
    mypaper.rect(px + 18, py + 27, 4, 50 * (that.ninput - 1) - 7 - 7).attr(clineattr); // cline
    const rect = mypaper.rect(px, py + 50, 40, 40, 10).attr(rectattr); // crect
    rect.attr({ fill: purple });

    return mypaper;
  }

  show(gpos) {
    const that = this;
    that.formEdit_reload(obj[gpos].gparams);
    that.windowContainer.show();
    that.paperGate_reload();
  }

  close() {
    const that = this;
    that.windowContainer.hide();
  }
}

class GateInputer {
  constructor() {
    this.ninput = 1;
    this.init();
  }

  init() {
    const that = this;
    that.windowContainer = that.windowContainer_create();
    that.windowContainer_event();
    that.layoutContainer = that.layoutContainer_create();
    that.formEdit = that.formEdit_create();
    // that.formEdit_event();

    // 挂载
    that.windowContainer.attach(that.layoutContainer);
    that.layoutContainer.getCell('formContainer').attach(that.formEdit);
  }

  windowContainer_create() {
    const mywindow = new WindowDHX({
      modal: true,
      width: 300,
      height: 250,
    });
    return mywindow;
  }

  windowContainer_event() {
    const that = this;
    const myevents = that.windowContainer.events;
    myevents.on('afterhide', () => {
      console.log('return from gatepos: %d', onmodifypos);
      // 保存选项
      for (let i = 0; i < that.ninput; ++i) {
        ginput[i] = that.formEdit.getItem(`Q${i.toString()}`).getValue();
      }
      for (let i = 0; i < that.ninput; ++i) {
        console.log(ginput[i]);
      }
      obj[onmodifypos].afterEdit();
      that.close();
      refreshCode();
    });
  }

  layoutContainer_create() {
    const mylayout = new LayoutDHX('layout', {
      type: 'none',
      cols: [
        {
          id: 'formContainer',
          width: '100%',
          height: '100%',
        },
      ],
    });
    return mylayout;
  }

  formEdit_create() {
    const myform = new FormDHX('form', formArray[1]);
    return myform;
  }

  // TODO
  // eslint-disable-next-line no-unused-vars
  formEdit_reload(gateinput) {
    const that = this;
    if (obj[onmodifypos].gname == 'rz') {
      that.formEdit.getItem('Q0').setProperties({ label: 'lambda' });
    }
    for (let i = 0; i < that.ninput; ++i) {
      that.formEdit.getItem(`Q${i.toString()}`).setValue('pi/2');
    }
  }

  show(gpos) {
    const that = this;
    that.formEdit_reload(obj[gpos].gparams);
    that.windowContainer.show();
  }

  close() {
    const that = this;
    that.windowContainer.hide();
  }
}

const gatepicker = new GatePicker();
const gateinputer = new GateInputer();
const qsimulator = new QSimulator(maxgate);

// var gateeditor = new Array(gateinputer, gatepicker);

// QGate //////////////////////////////////////////

class QGate {
  constructor(canvas, px, py, ginfo, color, copyable) {
    // 从输入参数中获取
    this.canvas = canvas;
    this.px = px;
    this.py = py;
    this.type = 'Gate';
    this.color = color;
    this.copyable = copyable;
    this.pos = obj.length;
    // 从编译器返回值ginfo中获取
    this.gid = ginfo.id;
    this.gname = ginfo.name;
    this.gtype = ginfo.type;
    this.ginput = ginfo.input;
    this.gparam = ginfo.param;
    this.guserid = ginfo.userid;
    this.gcode = ginfo.code;
    if (copyable == 1) { // 说明是从数据库中加载的ginfo，部分字段是缺失的，需要手动补充
      switch (this.gname) {
        case 'cx': case 'cy': case 'cz': case 'swap':
          this.gparams = [0, 1];
          break;
        default:
          this.gparams = [0];
          break;
      }
    } else {
      this.gparams = ginfo.params;
    }
    // 输入的顺序（把参数给撇掉）
    this.ginputs = this.gparams.slice(this.gparam);
    this.ginputs.sort();
    this.gheit = Math.abs(this.ginputs[0] - this.ginputs[this.ginputs.length - 1]); // 获取inputs最大跨度
    this.theta = 'pi/2'; // RX, RY, RZ // 不会显示在线路上，但直接用于生成代码
    this.patternInit(canvas, color);
  }

  patternInit(canvas, color) {
    const that = this;
    that.allele = canvas.set(); // all elements set
    // ===== 1. 拖动时展示的曲线边框 =====
    // 根据参数来设置rectframe的高度
    that.rectframe = canvas.rect(that.px, that.py, 40, 40 + 50 * that.gheit, 10).attr(rectframeattr);
    that.rectframe.hide();
    that.allele.push(that.rectframe);
    // ===== 2. 在面板/线路中显示的形状 =====
    switch (that.gname) { // 属性和文字标记
      case 'sdg': case 'tdg':
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.text = canvas.set();
        that.text1 = canvas.text(20, 20, that.gname[0].toUpperCase()).attr(textattr);
        that.text2 = canvas.path(daggerPath).attr({ stroke: white, 'stroke-width': 2 });
        that.text.push(that.text1, that.text2);
        that.text.transform(`t${that.px}, ${that.py}`);
        break;
      case 'rx': case 'ry': case 'rz':
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.rect.attr({ fill: deepblue });
        that.text = canvas.text(that.px + 20, that.py + 20, that.gname.toUpperCase()).attr(textattr);
        break;
      case 'cx': case 'cy': case 'cz':
        that.crect = canvas.rect(that.px + 13, getyPos(that.gparams[0]) + 13, 14, 14, 3).attr(crectattr);
        that.cline = canvas.rect(that.px + 18, getyPos(that.ginputs[0]) + 20, 4, 50 * that.gheit).attr(clineattr); // 从最小的bit开始
        that.allele.push(that.crect, that.cline);
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.rect.attr({ fill: purple });
        that.text = canvas.text(that.px + 20, that.py + 20, that.gname.toUpperCase()).attr(textattr);
        if (that.copyable) { // 在gate panel上的不显示控制比特
          that.crect.hide();
          that.cline.hide();
        } else {
          that.text.attr({ text: that.gname[1].toUpperCase() });
        }
        break;
      case 'swap':
        that.uparrow = canvas.path(uparrowPath).attr(swapattr); // 上箭头
        that.downarrow = canvas.path(downarrowPath).attr(swapattr); // 下箭头
        that.cline = canvas.rect(that.px + 18, getyPos(that.ginputs[0]) + 20, 4, 50 * that.gheit).attr(clineattr); // 从最小的bit开始
        that.swapset = canvas.set();
        that.swapset.push(that.uparrow, that.downarrow, that.cline);
        that.allele.push(that.swapset);
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.text = canvas.path(swapPath).attr({ stroke: white, 'stroke-width': 2 });
        that.text.transform(`t${that.px}, ${that.py}`);
        that.rect.attr({ fill: purple });
        if (that.copyable) {
          that.swapset.hide();
        } else {
          console.log('in[0]: %d, in[1]: %d', that.ginputs[0], that.ginputs[1]);
          that.uparrow.transform(`t${that.px}, ${getyPos(that.ginputs[0])}`);
          that.downarrow.transform(`t${that.px}, ${getyPos(that.ginputs[1])}`);
          that.rect.hide();
          that.text.hide();
        }
        break;
      case 'measure':
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.rect.attr({ fill: orange });
        that.text = canvas.path(measurePath).attr({ stroke: white, 'stroke-width': 2 });
        that.text.transform(`t${that.px}, ${that.py}`);
        break;
      default:
        that.rect = canvas.rect(that.px, that.py, 40, 40, 10).attr(rectattr); // 基础小方块
        that.text = canvas.text(that.px + 20, that.py + 20, that.gname.toUpperCase()).attr(textattr);
        break;
    }
    that.rectset = canvas.set();
    that.rectset.push(that.rect, that.text);
    that.allele.push(that.rect, that.text);
    // ===== 3. hover in的时候显示的门矩阵提示 =====
    that.gmatrix = gmatrix[that.gid];

    // 左上角编辑小标记
    that.edit_rect = canvas.rect(that.px - 9, that.py - 9, 15, 15, 15).attr(circleattr);
    that.edit = canvas.path(editPath).attr({ stroke: '#333' });
    that.editset = canvas.set();
    that.editset.push(that.edit_rect, that.edit);
    that.editset.hide();
    // 所有gate都有右上角删除小标记
    that.dele_rect = canvas.rect(that.px + 34, that.py - 9, 15, 15, 15).attr(circleattr);
    that.dele = canvas.path(delePath).attr({ fill: '#333', stroke: '#333' });
    that.deleset = canvas.set();
    that.deleset.push(that.dele_rect, that.dele);
    that.deleset.hide();
    that.allele.push(that.edit_rect, that.edit, that.dele_rect, that.dele);
    // 相关函数 ====================================
    // ===== 鼠标移入时显示门矩阵信息
    function onhoverin() {
      if (that.copyable) {
        // 获取坐标
        const canvasRect = canvas.canvas.getBoundingClientRect();
        // 显示提示
        const formulaDiv = document.createElement('div');
        formulaDiv.innerHTML = `$$${that.gmatrix}$$`;
        formulaDiv.id = 'formula-div';
        formulaDiv.style.position = 'absolute';// that.px, that.py + 50,
        formulaDiv.style.left = `${canvasRect.x + that.px}px`; // 获取
        formulaDiv.style.top = `${canvasRect.y + 50 + that.py}px`;
        formulaDiv.style.backgroundColor = '#fff';
        document.body.appendChild(formulaDiv);
        typeset(formulaDiv);
      }
    }
    // 鼠标移出后隐藏门矩阵信息
    function onhoverout() {
      const formulaDiv = document.getElementById('formula-div');
      if (formulaDiv && formulaDiv.parentNode) {
        formulaDiv.parentNode.removeChild(formulaDiv);
      }
    }
    // 判断新 rect 是否在线路上
    function oncircuit(rx, ry) {
      const d = 30;
      const linex = getCol(rx);
      const liney = getRow(ry);
      if (!(linex >= 0 && linex < maxgate && liney >= 0 && liney < nline && liney + that.gheit < nline)) { return false; }
      const sx = 140 + linex * 60;
      const sy = 160 + liney * 50;
      return (circuit[liney][linex] == -1 && sx - d < rx && rx < sx + d && sy - d < ry && ry < sy + d);
    }
    // /////////////////////////////////////////////
    // 拖动gate的相关函数：onmove onstart onstop
    // /////////////////////////////////////////////
    // 在拖到可放置位置的时候显示曲线边框
    function updateframe() {
      const rectx = that.rect.attr('x');
      const recty = that.rect.attr('y');
      if (oncircuit(rectx, recty)) {
        that.rectframe.attr({
          x: 140 + getCol(rectx) * 60,
          y: 160 + getRow(recty) * 50,
        });
        that.rectframe.show();
      } else {
        that.rectframe.hide();
      }
    }
    // move
    function onmove(dx, dy) {
      that.rect.attr({ x: that.px + dx, y: that.py + dy });
      switch (that.gname) {
        case 'sdg': case 'tdg': case 'swap': case 'measure':
          that.text.transform(`t${that.px + dx}, ${that.py + dy}`);
          break;
        default:
          that.text.attr({ x: that.px + dx + 20, y: that.py + dy + 20 });
          break;
      }
      updateframe();
    }
    // 开始拖动一个gate
    function onstart() {
      if (that.copyable) { // 从面板上拖动的，需要初始化一个新gate放在原地
        const newgate = new QGate(canvas, that.px, that.py, basicGates[that.gid - 1], color, 1);
        obj.push(newgate);
      }
      // TODO：cx 等要能拖到上下的位置，circuit[getRow(that.py)][getCol(that.px)] = -1;
      switch (that.gname) {
        case 'cx': case 'cy': case 'cz': // 拖动的时候只显示基础小方块
          that.crect.hide();
          that.cline.hide();
          that.text.attr({
            text: that.gname.toUpperCase(),
          });
          break;
        case 'swap': // TODO：拖动的时候显示基础小方块
          break;
        default: break;
      }
    }
    // 停止拖动一个gate
    function onstop() {
      if (oncircuit(that.rect.attr('x'), that.rect.attr('y'))) { // 拖到位了，显示在线路上
        if (that.copyable == 0) { // move from circuit
          console.log('move from circuit onstop');
          circuit[getRow(that.py)][getCol(that.px)] = -1; // 原来的位置标记为-1
          // -- ngate[getRow(that.py)];   // remove old gate
        }
        // 获取新的px、py
        that.px = that.rectframe.attr('x');
        that.py = that.rectframe.attr('y');
        // 更新输入比特
        const delta = getRow(that.rectframe.attr('y')) - that.ginputs[0]; // 位移
        for (let i = that.gparam; i < that.gparam + that.ginput; ++i) {
          that.gparams[i] += delta;
        }
        that.ginputs = that.gparams.slice(that.gparam); // 更新输入比特顺序
        that.ginputs.sort();
        // 显示在线路上的图形
        switch (that.gname) {
          case 'sdg': case 'tdg': case 'measure':
            that.rect.attr({ x: that.px, y: that.py });
            that.text.transform(`t${that.px}, ${that.py}`);
            break;
          case 'cx': case 'cy': case 'cz':
            that.py = getyPos(that.gparams[1]);
            that.crect.attr({ x: that.px + 13, y: getyPos(that.gparams[0]) + 13 }); // 控制比特
            that.cline.attr({ x: that.px + 18, y: getyPos(that.ginputs[0]) + 20 }); // 控制线
            that.crect.show();
            that.cline.show();
            that.rect.attr({ x: that.px, y: that.py }); // 受控比特
            that.text.attr({ // 小方块上显示的字
              x: that.px + 20,
              y: that.py + 20,
              text: that.gname[1].toUpperCase(),
            });
            break;
          case 'swap': // TODO：uparrow, downarrow, line
            that.uparrow.attr({ x: that.px + 20, y: getyPos(that.ginputs[0]) }); // 上箭头
            that.downarrow.attr({ x: that.px + 20, y: getyPos(that.ginputs[1]) }); // 下箭头
            that.cline.attr({ x: that.px + 20, y: getyPos(that.ginputs[0]) + 20 }); // 控制线
            that.swapset.show();
            that.rect.hide();
            that.text.hide();
            break;
          default:
            that.rect.attr({ x: that.px, y: that.py });
            that.text.attr({ x: that.px + 20, y: that.py + 20 });
            break;
        }
        that.rectframe.hide();
        // update circuit
        circuit[getRow(that.py)][getCol(that.px)] = that.pos;
        that.copyable = 0;
        // ++ ngate[getRow(that.py)];
      } else { // 没拖到位
        // eslint-disable-next-line no-lonely-if
        if (that.copyable) { // move from gate panel
          that.allele.remove();
        } else { // stay the same
          switch (that.gname) {
            case 'sdg': case 'tdg': case 'measure':
              that.rect.attr({ x: that.px, y: that.py });
              that.text.transform(`t${that.px}, ${that.py}`);
              break;
            case 'cx': case 'cy': case 'cz':
              that.rect.attr({ x: that.px, y: that.py });
              that.text.attr({ // 小方块上显示的字
                x: that.px + 20,
                y: that.py + 20,
                text: that.gname[1].toUpperCase(),
              });
              that.crect.show();
              that.cline.show();
              break;
            default:
              that.rect.attr({ x: that.px, y: that.py });
              that.text.attr({ x: that.px + 20, y: that.py + 20 });
              break;
          }
        }
      }
      refreshCode();
    }
    function onclickedit() {
      onmodifypos = that.pos;
      // eslint-disable-next-line no-empty
      if (that.gtype == 0) {

      } else if (that.gtype == 2) { // rx ry rz
        gateinputer.show(that.pos);
      } else if (that.gtype == 3) { // swap cx cy cz
        gatepicker.show(that.pos);
      }
    }
    function onclickdele() {
      if (!that.copyable) {
        const linex = getCol(that.px);
        const liney = getRow(that.py);
        // -- ngate[liney];
        circuit[liney][linex] = -1;
        that.allele.remove();
        refreshCode();
      }
    }
    // 显示编辑/删除
    function onclickrect() {
      console.log('onclick rect');
      if (that.copyable) return;
      if (that.gtype != 0 && that.gtype != 1) {
        // edit
        that.edit_rect.attr({ x: that.px - 9, y: that.py - 9 });
        that.edit.transform(`t${(that.px - 17).toString()},${(that.py - 17).toString()}s0.35`);
        that.editset.show();
      }
      // delete
      that.dele_rect.attr({ x: that.px + 34, y: that.py - 9 });
      that.dele.transform(`t${(that.px + 25).toString()},${(that.py - 17).toString()}s0.4`);
      that.deleset.show();
    }
    function onmousedown() {
      console.log('onmousedown');
    }

    // 挂载各种回调函数
    that.rectset.drag(onmove, onstart, onstop);
    that.rectset.hover(onhoverin, onhoverout);
    that.rectset.click(onclickrect); // click rect
    that.editset.click(onclickedit); // click edit
    that.deleset.click(onclickdele); // click dele
    that.rectset.touchend(onmousedown);
  }

  removeAll() {
    const that = this;
    that.allele.remove();
  }

  moveUp() {
    const that = this;
    for (let i = that.gparam; i < that.gparam + that.ginput; ++i) {
      that.gparams[i] -= 1;
    }
    that.ginputs = that.gparams.slice(that.gparam); // 更新输入比特顺序
    that.ginputs.sort();
    that.allele.transform('t0,-50'); // TODO：inputs, params都要改
  }

  // 修改了东西
  afterEdit() {
    const that = this;
    // 完成修改，隐藏 editset, deleset
    that.editset.hide();
    that.deleset.hide();
    if (that.gtype == 0) {
      return;
    }
    if (that.gtype == 2) { // rx ry rz
      // eslint-disable-next-line prefer-destructuring
      that.theta = ginput[0];
      console.log(that.theta);
      return;
    }
    // remove from old circuit
    circuit[getRow(that.py)][getCol(that.px)] = -1;
    // -- ngate[getRow(that.py)];   // remove old gate

    const cypos = getyPos(ginput[0]);
    const ypos = getyPos(ginput[1]);

    that.py = ypos;
    that.gheit = getRow(ypos) - getRow(cypos);

    console.log('gheit = %d', that.gheit);

    // rect, text
    that.rect.attr({ y: ypos });
    that.text.attr({ y: ypos + 20 });
    // crect, cline
    that.crect.attr({ y: cypos + 13 });
    that.cline.attr({ height: Math.abs(ypos - cypos) - 14 });
    that.cline.attr({ y: that.gheit > 0 ? cypos + 27 : ypos + 27 });

    // rectframe
    that.rectframe.attr({
      y: cypos + 27,
      height: Math.abs(ypos - cypos) + 50,
    });
    // gateinput
    for (let i = 0; i < that.ginput; ++i) {
      that.gateinput[i] = ginput[i];
    }

    circuit[getRow(that.py)][getCol(that.px)] = that.pos;
    // ++ ngate[getRow(that.py)];
  }

  setColor(rectclr, textclr) {
    const that = this;
    that.rect.attr({ fill: rectclr });
    if (that.ginput > 1) {
      that.crect.attr({ fill: rectclr });
      that.cline.attr({ fill: rectclr });
    }
    that.text.attr({ fill: textclr });
  }
}

class QLine {
  constructor(canvas, py, id, color) {
    this.type = 'Line';
    this.color = color;
    this.lid = id;
    this.color = color;
    this.pos = obj.length;
    this.patternInit(canvas, py);
  }

  patternInit(canvas, py) {
    const that = this;
    // Q[i]
    that.lname = `Q[${that.lid.toString()}]`;
    that.text = canvas.text(45, py - 2, that.lname).attr({
      fill: that.color, // black
      'font-size': 15,
    });
    // line
    const line_path = `M110,${py.toString()}H1000`;
    that.qline = canvas.path(line_path).attr({
      fill: black,
      stroke: black,
    });
    // trash
    const trash_y = py - 15;
    that.trash = canvas.path(trashPath).attr({
      fill: black,
      stroke: black,
    });
    that.trash.transform(`t70,${trash_y.toString()}s0.7`);
    that.trash.hide();
    // trash rect
    that.trash_rect = canvas.rect(80, py - 8, 11, 16).attr({
      fill: 'rgba(0, 0, 0, 0)',
      'stroke-width': 0,
      cursor: 'pointer',
    });
    // mouseover
    that.trash_rect.mouseover(() => {
      that.trash.show();
    });
    that.trash_rect.mouseout(() => {
      that.trash.hide();
    });
    // remove qubit
    function onremove() {
      console.log('trash clicked', that.lid);
      if (nline == 1) { return; }
      // remove gates
      for (let i = 0; i < maxgate; ++i) {
        const pos = circuit[that.lid][i];
        if (pos != -1) {
          obj[pos].removeAll();
        }
      }
      // gate 上移
      for (let i = that.lid + 1; i < nline; ++i) {
        // ngate[i-1] = ngate[i];
        for (let j = 0; j < maxgate; ++j) {
          circuit[i - 1][j] = circuit[i][j];
          const pos = circuit[i][j];
          if (pos != -1) {
            obj[pos].moveUp();
          }
        }
      }
      --nline;
      // ngate[nline] = 0;
      for (let j = 0; j < maxgate; ++j) {
        circuit[nline][j] = -1;
      }
      const pos = line[nline];
      obj[pos].hideAll();
      refreshCode();
    }
    // TODO：调用fcompile重新绘图
    // click trash
    that.trash_rect.click(onremove);
    that.trash.click(onremove);
  }

  hideAll() {
    const that = this;
    that.text.hide();
    that.qline.hide();
    that.trash_rect.hide();
    that.trash.hide();
  }

  showAll() {
    const that = this;
    that.text.show();
    that.qline.show();
    that.trash_rect.show();
  }

  setColor(clr) {
    const that = this;
    that.text.attr({ fill: clr });
    that.qline.attr({ fill: clr, stroke: clr });
    that.trash.attr({ fill: clr, stroke: clr });
  }
}

class QCircuit {
  constructor(canvas, cir = null, bgs = null) {
    this.canvas = canvas;
    this.type = 'Circuit';
    this.pos = obj.length;
    this.colorpicker = new ColorPicker();
    this.posline = 180;
    // background
    this.background = canvas.rect(0, 0, 1200, 600).attr({
      fill: grey,
      'stroke-width': 0,
    });
    this.background.click(unfocus);

    obj.push(this);

    // 初始化 line 和 circuit obj数组
    for (let i = 0; i < maxline; ++i) {
      // ngate[i]   = 0;
      line[i] = -1;
    }
    for (let i = 0; i < maxgate; ++i) {
      circuit[i] = new Array(maxgate);
      for (let j = 0; j < maxgate; ++j) {
        circuit[i][j] = -1;
      }
    }

    // 获取线路量子比特数
    if (cir == null) {
      nline = 4;
    } else { // 根据 cir 参数新建线路
      // console.log(cir);
      nline = cir.nqubit;
    }

    // line, default lines
    for (let i = 0; i < maxline; ++i) {
      const newline = new QLine(canvas, this.posline + i * 50, i, black);
      obj.push(newline);
      line[i] = newline.pos;
      if (i >= nline) {
        newline.hideAll();
      }
    }

    // 保存该用户的门
    basicGates = bgs;
    // console.log(basicGates);

    // 初始化gate panel
    let pos = 20;
    for (let i = 0; i < basicGates.length; ++i) {
      const newgate = new QGate(canvas, pos, 20, basicGates[i], blue, 1); // copyable
      obj.push(newgate);
      pos += 50;
    }

    // TODO：加载用户自定义的gate

    // 根据cir.circuit添加gate
    if (cir != null) {
      for (let i = 0; i < nline; ++i) {
        for (let j = 0; cir.circuit[i] != null && j < cir.circuit[i].length; ++j) {
          const gid = cir.circuit[i][j].id;
          if (gid == 0) { // 补位用的gate跳过
            continue;
          }
          const px = getxPos(j); // 获取横坐标
          const py = getyPos(i); // 获取纵坐标
          const newgate = new QGate(canvas, px, py, cir.circuit[i][j], blue, 0);
          obj.push(newgate);
          // update circuit
          circuit[getRow(py)][getCol(px)] = newgate.pos;
        }
      }
    }
  }

  getGate() {
    console.log('nobj: %d', obj.length);
    console.log('nline: ', nline);
    for (let i = 0; i < nline; ++i) {
      console.log('qubit ', i);
      for (let j = 0; j < maxgate; ++j) {
        if (circuit[i][j] != -1) {
          console.log(circuit[i][j]);
        }
      }
    }
  }

  // 构造线路抽象提交给后端模拟器
  getCircuit() {
    const circuitJson = {};
    circuitJson.nqubit = nline;
    circuitJson.circuit = new Array(nline);
    for (let i = 0; i < nline; ++i) {
      circuitJson.circuit[i] = new Array();
      for (let j = 0; j < maxgate; ++j) {
        if (circuit[i][j] != -1) {
          const currGate = obj[circuit[i][j]];
          const gateDto = JSON.parse(JSON.stringify(basicGates[currGate.gid - 1]));
          // 需要额外构造的
          gateDto.params = currGate.gparams;
          circuitJson.circuit[i].push(gateDto);
        } else {
          circuitJson.circuit[i].push({ id: 1 }); // 补ID gate
        }
      }
    }
    console.log('circuitJson', circuitJson);
    return circuitJson;
  }

  addQubit() {
    console.log('addQubit');
    if (nline == maxline) { return; }
    obj[line[nline++]].showAll();
    refreshCode();
  }

  getProbability() {
    qsimulator.init(nline, circuit, obj);
    const prob = qsimulator.getProb();
    return prob;
  }

  getQState() {
    const qstate = qsimulator.getQState();
    return qstate;
  }

  setBGColor(clr) {
    const that = this;
    that.background.attr({ fill: clr });
  }

  setColor(cjson) {
    console.log(cjson);
    for (let i = 0; i < obj.length; ++i) {
      console.log(obj[i].type);
      if (obj[i].type == 'Circuit') {
        obj[i].setBGColor(cjson.Background);
      } else if (obj[i].type == 'Line') {
        obj[i].setColor(cjson.Line);
      } else if (obj[i].type == 'Gate') {
        if (obj[i].gname == 'measure') { obj[i].setColor(cjson.Measure, cjson.Text); } else { obj[i].setColor(cjson.Gate, cjson.Text); }
      }
    }
  }

  getCode() {
    return refreshCode();
  }
}

export {
  QGate, QLine, QCircuit,
};
